﻿using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Runtime.Remoting;
using MudEngine.Runtime;
using System;

namespace MudEngine
{
	public class CommunicationManager
	{
        public const string MASTER_CONNECT = "connect";

        public const string APPLY_LOGON = "logon";
        public const string APPLY_PROCESS_INPUT = "process_input";
        /// <summary>
        /// 
        /// </summary>
        private TcpListener _listener;
        /// <summary>
        /// 
        /// </summary>
        private int _port;
		/// <summary>
		/// 
		/// </summary>
		private object_t _master;
        /// <summary>
        /// 
        /// </summary>
        private TcpConnection[] _users = null;
        private int _maxUsers = 10;

        public Interpreter _interpreter { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public CommunicationManager(int port, int maxUsers, object_t master)
        {
            _port = port;
            _master = master;
            _maxUsers = maxUsers;

            _users = new TcpConnection[_maxUsers];

            IPEndPoint iep = new IPEndPoint(IPAddress.Any, port);
            _listener = new TcpListener(iep);
        }
		/// <summary>
		/// 
		/// </summary>
		private object _lock = new object();
        /// <summary>
        /// 
        /// </summary>
        public void ProcessIO()
        {
            // accept incoming connection
            if (_listener.Pending())
            {
                TcpConnection newConnection = new TcpConnection(this._listener.AcceptTcpClient());
                newConnection.OnConnect += newConnection_OnConnect;
                newConnection.OnDisconnect += newConnection_OnDisconnect;
                newConnection.OnTelnetCommand += newConnection_OnTelnetCommand;
                newConnection.HandleConnection();
            }

            // handle commands
            TcpConnection user = null;
            for (int i = 0; i < _maxUsers; i++)
            {
                if (_users[i] == null)
                    continue;

                user = _users[i];
                if (user.HasMessage)
                {
                    TcpMessage nextMessage = user.GetNextMessage();
                    Log.Debug(this.GetType(), string.Format("Process '{0}' for user '{1}'", nextMessage.Message, user.Object.Name));

                    _interpreter.Context.SaveCaller(user.Object);
                    _interpreter.Context.Push(nextMessage.Message);
                    _interpreter.Apply(user.Object, APPLY_PROCESS_INPUT, 1);
                    _interpreter.Context.RestoreCaller();
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        void newConnection_OnDisconnect(TcpConnection sender)
        {
            // clean up user
            sender.Object.TcpConnection = null;
            sender.Object.Destruct();
            sender.Object = null;

            // .... do something here.

            // remove user from list
            _users[sender.ConnectionId] = null;
        }
        /// <summary>
        /// 
        /// </summary>
        public void Start()
        {
            _listener.Start();

            Log.Info(this.GetType(), "Server started [" + _port + "]");
        }

		void newConnection_OnTelnetCommand(TcpConnection sender, TelnetEventArgs args)
		{
			//this.Ob.CALL_FUNCTION_BY_ADDRESS("SetEnvVar", "WIDTH", width);
			//this.Ob.CALL_FUNCTION_BY_ADDRESS("SetEnvVar", "HEIGHT", height);
		}

        private int StoreInEmptySlot(TcpConnection user)
        {
            lock (_lock)
            {
                for (int i = 0; i < _users.Length; i++)
                    if (_users[i] == null)
                    {
                        _users[i] = user;
                        user.ConnectionId = i;
                        return i;
                    }
            }
            return -1;
        }

        void newConnection_OnConnect(TcpConnection sender)
		{
            int slot = StoreInEmptySlot(sender);
            if (slot == -1)
            {
                // Disconnect connection and send message to user to be disconnected because server is full
                return;
            }

            _master.TcpConnection = sender;
            _master.TcpConnection.Object = _master;
            _master.Flags |= ObjectFlags.O_INTERACTIVE;
            _interpreter.Context.SetCaller(_master);

            _interpreter.Context.Push(sender.Port);
            Value ret = _interpreter.Apply(_master, MASTER_CONNECT, 1);

            _interpreter.Context.SetCaller(null);
            _master.Flags &= ~ObjectFlags.O_INTERACTIVE;
            _master.TcpConnection = null;

            // assume returned object is the player object to continue with
			if (ret.Object != null)
			{
				sender.Object = ret.Object;
				sender.Object.TcpConnection = sender;
				sender.Object.Flags |= ObjectFlags.O_INTERACTIVE;

                _interpreter.Context.SetCaller(sender.Object);
                _interpreter.Apply(sender.Object, APPLY_LOGON, 0);
                _interpreter.Context.SetCaller(null);
			}
			else
			{
				// oeps
                sender.Disconnect();
			}
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="connection"></param>
		/// <returns></returns>
		public bool Disconnect(object_t obj)
		{
            if (!obj.Flags.HasFlag(ObjectFlags.O_INTERACTIVE))
                return false;

            if (obj.TcpConnection.Disconnect())
                return true;
			return false;
		}
	}
}
